package com.gitee.kenewstar.utils;

import static com.gitee.kenewstar.utils.CollectUtil.*;

import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.Inherited;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * 反射工具
 *
 * @author kenewstar
 * @date 2022/2/22
 */
public abstract class ReflectUtil {

	protected static boolean isMetaAnnotation(Class<? extends Annotation> annotationType) {
		return Documented.class.equals(annotationType) || Retention.class.equals(annotationType)
				|| Inherited.class.equals(annotationType) || Target.class.equals(annotationType)
				|| Repeatable.class.equals(annotationType);
	}

	protected static boolean isNotMetaAnnotation(Class<? extends Annotation> annotationType) {
		return !isMetaAnnotation(annotationType);
	}

	protected static boolean isNotMetaAnnotation(Annotation annotation) {
		return isNotMetaAnnotation(annotation.annotationType());
	}

	public static <T extends AnnotatedElement> Predicate<T> withAnnotation(Class<? extends Annotation> annotationType) {
		return member -> Objects.nonNull(member) && member.isAnnotationPresent(annotationType);
	}

	/**
	 * 不是Object类
	 *
	 * @param <T> 范型
	 * @return predicate
	 */
	public static <T extends Class<?>> Predicate<T> notObject() {
		return clazz -> Objects.nonNull(clazz) && !Objects.equals(clazz, Object.class);
	}

	public static Set<Class<?>> getSuperTypes(Class<?> clazz) {
		Set<Class<?>> superTypes = new LinkedHashSet<>();
		Class<?> superclass = clazz.getSuperclass();
		Class<?>[] interfaces = clazz.getInterfaces();
		if (Objects.nonNull(superclass)) {
			superTypes.add(superclass);
		}
		if (interfaces.length > 0) {
			superTypes.addAll(Arrays.asList(interfaces));
		}
		return superTypes;
	}

	public static Set<Class<?>> getAllSuperTypes(Class<?> clazz) {
		Set<Class<?>> allSuperTypes = new LinkedHashSet<>();
		Set<Class<?>> superTypes = getSuperTypes(clazz);

		superTypes.stream()
				.peek(allSuperTypes::add)
				.map(ReflectUtil::getAllSuperTypes)
				.collect(Collectors.toSet())
				.forEach(allSuperTypes::addAll);

		return allSuperTypes;
	}


	public static Set<Field> getFields(Class<?> clazz) {
		return newHashSet(clazz.getDeclaredFields());
	}

	/**
	 * 获取类的所有属性, 包括父类中的属性，不包括Object类型
	 *
	 * @param clazz 类类型
	 * @return 属性集合
	 */
	public static Set<Field> getAllFields(Class<?> clazz) {
		Set<Field> fieldSet = new HashSet<>(getFields(clazz));

		getAllSuperTypes(clazz).stream()
				.filter(notObject())
				.map(Class::getDeclaredFields)
				.map(Arrays::asList)
				.collect(Collectors.toSet())
				.forEach(fieldSet::addAll);

		return fieldSet;
	}

	public static Set<Method> getMethods(Class<?> clazz) {
		return newHashSet(clazz.getDeclaredMethods());
	}

	public static Set<Method> getAllMethods(Class<?> clazz) {
		Set<Method> methodSet = new HashSet<>(getMethods(clazz));

		getAllSuperTypes(clazz).stream()
				.filter(notObject())
				.map(Class::getDeclaredMethods)
				.map(Arrays::asList)
				.collect(Collectors.toSet())
				.forEach(methodSet::addAll);

		return methodSet;
	}

	public static Set<Constructor<?>> getConstructors(Class<?> clazz) {
		return newHashSet(clazz.getDeclaredConstructors());
	}

	public static Set<Constructor<?>> getAllConstructors(Class<?> clazz) {
		Set<Constructor<?>> constructorSet = new HashSet<>(getConstructors(clazz));

		getAllSuperTypes(clazz).stream()
				.filter(notObject())
				.map(Class::getDeclaredConstructors)
				.map(Arrays::asList)
				.collect(Collectors.toSet())
				.forEach(constructorSet::addAll);

		return constructorSet;
	}

	/**
	 * 获取当前类上声明的注解
	 *
	 * @param clazz 当前类
	 * @return 注解集合
	 */
	public static Set<Annotation> getAnnotations(Class<?> clazz) {
		return newHashSet(clazz.getDeclaredAnnotations());
	}

	/**
	 * 获取当前类以及其父类上的注解
	 *
	 * @param clazz 当前类
	 * @return 注解集合
	 */
	public static Set<Annotation> getAllAnnotations(Class<?> clazz) {
		Set<Annotation> annotations = getAnnotations(clazz);

		getAllSuperTypes(clazz).stream()
				.map(ReflectUtil::getAnnotations)
				.filter(CollectUtil::isNotEmpty)
				.forEach(annotations::addAll);

		return annotations;
	}

	/**
	 * 获取当前类上的注解包括注解上的注解
	 * 但不包含元注解
	 *
	 * @param clazz 当前类
	 * @return 注解集合
	 */
	public static Set<Annotation> getAnnotationsWithSuper(Class<?> clazz) {
		Set<Annotation> annotationSet = new HashSet<>();

		getAnnotations(clazz).stream()
				.filter(ReflectUtil::isNotMetaAnnotation)
				.peek(annotationSet::add)
				.map(Annotation::annotationType)
				.map(ReflectUtil::getAnnotationsWithSuper)
				.filter(CollectUtil::isNotEmpty)
				.collect(Collectors.toSet())
				.forEach(annotationSet::addAll);

		return annotationSet;
	}

	public static Set<Method> getMethodsWithAnnotation(Class<?> clazz, Class<? extends Annotation> annotationType) {
		return getMethods(clazz).stream()
				.filter(withAnnotation(annotationType))
				.collect(Collectors.toSet());
	}

	public static Set<Method> getAllMethodsWithAnnotation(Class<?> clazz, Class<? extends Annotation> annotationType) {
		return getAllMethods(clazz).stream()
				.filter(withAnnotation(annotationType))
				.collect(Collectors.toSet());
	}

	public static Set<Field> getFieldsWithAnnotation(Class<?> clazz, Class<? extends Annotation> annotationType) {
		return getFields(clazz).stream()
				.filter(withAnnotation(annotationType))
				.collect(Collectors.toSet());
	}

	public static Set<Field> getAllFieldsWithAnnotation(Class<?> clazz, Class<? extends Annotation> annotationType) {
		return getAllFields(clazz).stream()
				.filter(withAnnotation(annotationType))
				.collect(Collectors.toSet());
	}

	public static Set<Constructor<?>> getConstructorsWithAnnotation(Class<?> clazz, Class<? extends Annotation> annotationType) {
		return getConstructors(clazz).stream()
				.filter(withAnnotation(annotationType))
				.collect(Collectors.toSet());
	}

	public static Set<Constructor<?>> getAllConstructorsWithAnnotation(Class<?> clazz, Class<? extends Annotation> annotationType) {
		return getAllConstructors(clazz).stream()
				.filter(withAnnotation(annotationType))
				.collect(Collectors.toSet());
	}



}
